package nf.pluginsys;

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;

import nf.modular.NFModule;
import nf.modular.NFModuleType;
import nf.modular.NFWiring;
import nf.modular.data.ClassificationResult;
import nf.modular.modclass.Classificator;
import nf.modular.modclass.ImageAnalyzer;
import nf.modular.modclass.ImageLoader;
import nf.modular.modclass.ImagePreprocessor;
import nf.modular.modclass.ResultExporter;
import nf.modular.modclass.Trainer;

public class Wiring implements NFWiring
{
private NFModule[] modules = new NFModule[NFModuleType.values().length];
	
	private ImageLoader mod_0_loader = null;
	private ImagePreprocessor mod_1_preprocessor = null;
	private ImageAnalyzer mod_2_analyzer = null;
	private Classificator mod_3_classificator = null;
	private ResultExporter mod_4_exporter = null;
	
	private Trainer mod_trainer = null;
	
	private boolean checked = false;
	
	public static class ModuleParameter
	{
		NFModuleType target = NFModuleType.Unknown;
		String name = null;
		int ival = 0;
		double dval = 0;
		String sval = null;
		
		public ModuleParameter(NFModuleType target, String name, int ival)
		{
			this.target = target;
			this.name = name;
			this.ival = ival;
		}
		
		public ModuleParameter(NFModuleType target, String name, double dval)
		{
			this.target = target;
			this.name = name;
			this.dval = dval;
		}
		
		public ModuleParameter(NFModuleType target, String name, String sval)
		{
			this.target = target;
			this.name = name;
			this.sval = sval;
		}
	}
	
	private ArrayList<ModuleParameter> paramConfig = new ArrayList<ModuleParameter>();
	
	public boolean isChecked()
	{
		return checked;
	}
	
	public ImageLoader getLoader()
	{
		return mod_0_loader;
	}
	
	public void setTrainer(Trainer mod_trainer)
	{
		if(this.mod_trainer == mod_trainer) return;
		if(this.mod_trainer != null) { this.mod_trainer.setWiring(null); }
		this.mod_trainer = mod_trainer;
		modules[NFModuleType.Trainer.ordinal()] = mod_trainer;
		modules[NFModuleType.Trainer.ordinal()].setWiring(this);
		//checked = false;
	}
	
	public Trainer getTrainer()
	{
		return mod_trainer;
	}
	
	public void setLoader(ImageLoader mod_0_loader)
	{
		if(this.mod_0_loader == mod_0_loader) return;
		if(this.mod_0_loader != null) { this.mod_0_loader.setWiring(null); }
		this.mod_0_loader = mod_0_loader;
		modules[0] = mod_0_loader;
		modules[0].setWiring(this);
		checked = false;
	}
	
	public ImagePreprocessor getPreprocessor()
	{
		return mod_1_preprocessor;
	}
	
	public void setPreprocessor(ImagePreprocessor mod_1_preprocessor)
	{
		if(this.mod_1_preprocessor == mod_1_preprocessor) return;
		if(this.mod_1_preprocessor != null) { this.mod_1_preprocessor.setWiring(null); }
		this.mod_1_preprocessor = mod_1_preprocessor;
		modules[1] = mod_1_preprocessor;
		modules[1].setWiring(this);
		checked = false;
	}
	
	public ImageAnalyzer getAnalyzer()
	{
		return mod_2_analyzer;
	}
	
	public void setAnalyzer(ImageAnalyzer mod_2_analyzer)
	{
		if(this.mod_2_analyzer == mod_2_analyzer) return;
		if(this.mod_2_analyzer != null) { this.mod_2_analyzer.setWiring(null); }
		this.mod_2_analyzer = mod_2_analyzer;
		modules[2] = mod_2_analyzer;
		modules[2].setWiring(this);
		checked = false;
	}
	
	public Classificator getClassificator()
	{
		return mod_3_classificator;
	}
	
	public void setClassificator(Classificator mod_3_classificator)
	{
		if(this.mod_3_classificator == mod_3_classificator) return;
		if(this.mod_3_classificator != null) { this.mod_3_classificator.setWiring(null); }
		this.mod_3_classificator = mod_3_classificator;
		modules[3] = mod_3_classificator;
		modules[3].setWiring(this);
		checked = false;
	}
	
	public ResultExporter getExporter()
	{
		return mod_4_exporter;
	}
	
	public void setExporter(ResultExporter mod_4_exporter)
	{
		if(this.mod_4_exporter == mod_4_exporter) return;
		if(this.mod_4_exporter != null) { this.mod_4_exporter.setWiring(null); }
		this.mod_4_exporter = mod_4_exporter;
		modules[4] = mod_4_exporter;
		modules[4].setWiring(this);
		checked = false;
	}
	
	public NFModule[] getModules()
	{
		return modules;
	}
	
	private NFModule firstProblem = null;
	
	public NFModule getFirstProblem()
	{
		return firstProblem;
	}
	
	public boolean checkModules()
	{
		firstProblem = null;
		
		System.out.println("Starting wiring check...");
		
		// last is unknown, so -2
		for(int i = modules.length - 3; i >= 0; --i)
		{
			if(modules[i] == null)
			{
				System.out.println("Module slot " + i + " is empty!");
				return false;
			}
			
			if(!modules[i].checkRequirements())
			{
				System.out.println("Module " + modules[i].toString() + " doesn't have requirements satisfied.");
				firstProblem = modules[i];
				return false;
			}
			
			if(!modules[i].checkWiring(this))
			{
				System.out.println("Module " + modules[i].toString() + " doesn't support current wiring configuration.");
				firstProblem = modules[i];
				return false;
			}
			
			System.out.println("Module " + modules[i].toString() + " is OK.");
		}
		
		if(!mod_3_classificator.acceptsDataType(mod_2_analyzer.getOutputType()))
		{
			System.out.println("Classificator [" + mod_3_classificator.toString() + "] doesn't support analyzer's [" + mod_2_analyzer.toString() + "] output type (" + mod_2_analyzer.getOutputType() + ")");
			firstProblem = mod_3_classificator;
			return false;
		}
		
		System.out.println("Everything checks out OK.");
		
		checked = true;
		return true;
	}
	
	private volatile boolean paramSetupPhase = false;
	
	public boolean performSetup()
	{
		if(!checked) return false;
		
		paramConfig.clear();
		paramSetupPhase = true;
		
		for(int i = modules.length - 3; i >= 0; --i)
		{
			if(!modules[i].init())
			{
				paramSetupPhase = false;
				System.out.println("Module " + modules[i].toString() + "' init FAILED.");
				return false;
			}
			
			if(!modules[i].setupParameters())
			{
				paramSetupPhase = false;
				System.out.println("Module " + modules[i].toString() + "' couldn't set its parameters.");
				return false;
			}
		}
		
		paramSetupPhase = false;
		return true;
	}
	
	public boolean requestParameter(NFModuleType target, String name, int ival, NFModule caller)
	{
		if(!paramSetupPhase) return false;
		if(!checked) return false;
		if(target == NFModuleType.Unknown) return false;
		if(name == null) return false;
	
		if(modules[target.ordinal()].setParamInt(name, ival))
		{
			System.out.println("Module '" + caller.toString() + "' sets int parameter '" + name + "' = '" + ival + "' to target " + target + ".");
			paramConfig.add(new ModuleParameter(target, name, ival));
			return true;
		}
		else
		{
			System.out.println("Module '" + caller.toString() + "' could't set int parameter '" + name + "' = '" + ival + "' to target " + target + ".");
			return false;
		}
	}
	
	public boolean requestParameter(NFModuleType target, String name, double dval, NFModule caller)
	{
		if(!paramSetupPhase) return false;
		if(!checked) return false;
		if(target == NFModuleType.Unknown) return false;
		if(name == null) return false;
	
		if(modules[target.ordinal()].setParamDouble(name, dval))
		{
			System.out.println("Module '" + caller.toString() + "' sets double parameter '" + name + "' = '" + dval + "' to target " + target + ".");
			paramConfig.add(new ModuleParameter(target, name, dval));
			return true;
		}
		else
		{
			System.out.println("Module '" + caller.toString() + "' could't set double parameter '" + name + "' = '" + dval + "' to target " + target + ".");
			return false;
		}
	}
	
	public boolean requestParameter(NFModuleType target, String name, String sval, NFModule caller)
	{
		if(!paramSetupPhase) return false;
		if(!checked) return false;
		if(target == NFModuleType.Unknown) return false;
		if(name == null) return false;
	
		if(modules[target.ordinal()].setParamString(name, sval))
		{
			System.out.println("Module '" + caller.toString() + "' sets string parameter '" + name + "' = '" + sval + "' to target " + target + ".");
			paramConfig.add(new ModuleParameter(target, name, sval));
			return true;
		}
		else
		{
			System.out.println("Module '" + caller.toString() + "' could't set string parameter '" + name + "' = '" + sval + "' to target " + target + ".");
			return false;
		}
	}
	
	public ClassificationResult processFile(String filePath, int threadID)
	{
		if(!checked)
		{
			System.out.println("Wiring isn't checked! Running a check...");
			if(!checkModules())
			{
				System.out.println("Wiring failed, aborting analysis.");
				return null;
			}
		}
		
		File file = new File(filePath);
		if(!file.exists())
		{
			System.out.println("File '" + filePath + "' doesn't exist, aborting analysis.");
			return null;
		}
		
		if(!file.isFile())
		{
			System.out.println("'" + filePath + "' isn't a regular file, aborting analysis.");
			return null;
		}
		
		String fileName = file.getName();
		
		BufferedImage sourceImage = mod_0_loader.loadImage(filePath);
		if(sourceImage == null)
		{
			System.out.println("Failed at loading image (loader is " + mod_0_loader.toString() + ")");
			return null;
		}
		
		BufferedImage preprocessedImage = mod_1_preprocessor.preprocessImage(sourceImage);
		if(preprocessedImage == null)
		{
			System.out.println("Failed at preprocessing image (preprocessor is " + mod_1_preprocessor.toString() + ")");
			return null;
		}
		sourceImage = null;
		
		int classification = -1;
		
		switch(mod_2_analyzer.getOutputType())
		{
		case OutBytes:
		{
			byte[] analysis = mod_2_analyzer.analyzeImageByte(preprocessedImage, fileName); 
			if(analysis == null)
			{
				System.out.println("Failed at image analysis (analyzer is " + mod_2_analyzer.toString() + ")");
				return null;
			}
			classification = mod_3_classificator.classifyData(analysis);
		}
		break;
			
		case OutDoubleArray:
		{
			double[] analysis = mod_2_analyzer.analyzeImageDouble(preprocessedImage, fileName); 
			if(analysis == null)
			{
				System.out.println("Failed at image analysis (analyzer is " + mod_2_analyzer.toString() + ")");
				return null;
			}
			classification = mod_3_classificator.classifyData(analysis);
		}
		break;
			
		case OutFile:
		{
			File analysis = mod_2_analyzer.analyzeImageFile(preprocessedImage, fileName); 
			if(analysis == null)
			{
				System.out.println("Failed at image analysis (analyzer is " + mod_2_analyzer.toString() + ")");
				return null;
			}
			classification = mod_3_classificator.classifyData(analysis);
		}
		break;
			
		case OutIntArray:
		{
			int[] analysis = mod_2_analyzer.analyzeImageInt(preprocessedImage, fileName); 
			if(analysis == null)
			{
				System.out.println("Failed at image analysis (analyzer is " + mod_2_analyzer.toString() + ")");
				return null;
			}
			classification = mod_3_classificator.classifyData(analysis);
		}
		break;
			
		case OutString:
		{
			String analysis = mod_2_analyzer.analyzeImageStr(preprocessedImage, fileName); 
			if(analysis == null)
			{
				System.out.println("Failed at image analysis (analyzer is " + mod_2_analyzer.toString() + ")");
				return null;
			}
			classification = mod_3_classificator.classifyData(analysis);
		}
		break;
		}
		
		ClassificationResult result = new ClassificationResult();
		result.accuracy = 0.0;
		result.error = classification < 0;
		result.type = classification;
		result.typeName = mod_3_classificator.getClassName(result.type);
		result.typeIcon = mod_3_classificator.getClassIcon(result.type);
		
		System.out.println("'" + fileName + "' successfully classified as '" + result.typeName + "'.");
		
		return result;
	}
}
